#pragma once

#include <iostream>
#include <queue>                /* 可以选择自定义实现队列 */
#include <pthread.h>            /* 此处使用系统调用 */
#include <mutex>
#include "lockGuard.hpp"

const int gDefaultCap = 5;
// #define INIT_TX(mtx) pthread_mutex_init(&mtx, nullptr)       /* 宏函数：实现互斥锁的初始化 */_
// #define INIT_CD(cond) pthread_cond_init(&cond, nullptr)      /* 宏函数：实现条件变量的初始化 */_

/* 阻塞队列的实现 */
template<class T>
class BlockQueue{
private:
    bool isQueueEmpty(){
        return _bq.size() == 0;
    }
    bool isQueueFull(){
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = gDefaultCap)
        :_capacity(capacity)
        {
            // INIT_TX(_mtx);
            // INIT_CD(_cond);
            pthread_mutex_init( &_mtx, nullptr);
            pthread_cond_init( &_Empty,nullptr);
            pthread_cond_init( &_Full,nullptr);
        }
    
    void push(const T &in){                              /* 生产者：上货【入队】 */
        // 被加锁区域就是：临界区
        //pthread_mutex_lock( &_mtx );                        /* 加锁 */
        lockGuard lockguard(&_mtx);

        // 1. 检测临界资源是否满足访问条件！
        while( isQueueFull() ){                 // if => while
            // 不满足，应等待！
            /* 
                提问：我们在临界区中使用：pthread_cond_wait 函数进行等待，此时，我是持有锁的！
                如果我去等待了！锁该怎么办？【此前的认知：锁被占用，此时其他线程都无法申请锁进行操作！】

                答：pthread_cond_wait 的函数的第二个参数是一个锁，
                    实际：当wait调用成功之后，传入的锁会被自动释放！

                提问：当我被唤醒时，我从哪里醒来？
                答：在哪里阻塞就从哪里唤醒！

                提问：我被唤醒时，仍然在临界区，锁从哪来？
                答：pthread_cond_wait 会自动帮助我们线程获取锁！
            */
            pthread_cond_wait( &_Full, &_mtx );             /* 发生阻塞 */
            /*
                提问：被挂起后，我怎么知道什么时候要唤醒？
                答：当阻塞队列不为满的时候，就可以唤醒了，但是我本身被挂起了，
                    无法参与检测，该检测行为：就由消费者进行提供！

                提问：如果函数 pthread_cond_wait 调用失败了怎么办？或伪唤醒怎么办？
                答：使用 while 进行条件检测！【原来是 if 语句】
            */
        }
        // 2. 访问临界资源
        _bq.push(in);

        // pthread_mutex_unlock( &_mtx );                      /* 解锁 */
        // pthread_cond_signal( &_Full );

        /* 实现：生产量达到总量的1/2时，再允许消费 */
        if( _bq.size() >= _capacity/2) pthread_cond_signal( &_Empty );
        //pthread_mutex_unlock( &_mtx );

    }

    void pop(T* out){                                       /* 消费者：购物【出队】 */
        //pthread_mutex_lock( &_mtx );
        lockGuard lockguard(&_mtx);

        // 1. 检测临界资源是否满足访问条件！
        while( isQueueEmpty() ){
            pthread_cond_wait( &_Empty, &_mtx );
        }

        // 2. 访问临界资源
        *out = _bq.front();
        _bq.pop();
        pthread_cond_signal( &_Full );
        //pthread_mutex_unlock( &_mtx );
        

    }

    ~BlockQueue()
    {
        pthread_mutex_destroy( &_mtx );
        pthread_cond_destroy( &_Empty );
        pthread_cond_destroy( &_Full );
    }

private:
    std::queue<T> _bq;          /* 阻塞队列 */
    int _capacity;              /* 阻塞队列的容量上限 */
    pthread_mutex_t _mtx;       /* 引入互斥锁保证队列的安全，防止同时增减造成的数据错误等问题 */
    pthread_cond_t _Empty;    /* 用于表征 bq 是否为空：消费者的关注点【空了不拿】 */
    pthread_cond_t _Full;     /* 用于表征 bq 是否满了：生产者的关注点【满了不放】 */
};

